#!/usr/bin/perl -w

BEGIN   
{
	sub getPoints{return '../../' if (!$_[1]);return '../' if (!$_[0]);return '/';}
	(my $file = __FILE__) =~ s/\/?(modules\/)?(nf_conntrack?\/)?(\w+\.pl)$/&getPoints($1,$2)/e;
	$file = './' if $file eq '/';
	unshift(@INC, $file."bin");
	unshift(@INC, $file."modules/nf_conntrack");
}

use strict;
use Data::Dumper;
use TWM;
use NFConnLogging;
use POSIX ":sys_wait_h";

my $HAS_POSIX = 1;
my (%maxsize, $data_shared);

my $nFConnLogging = NFConnLogging->new();
my $debug_level = $ck{debug_level};
my $chld_id = 'undefined';
my $current_day = (localtime)[3];

my $old_child_handler = $SIG{CHLD};
my %child_process = ();

$SIG{INT} = \&got_int;
$SIG{CHLD} = \&reaper;
$SIG{USR1} = sub
{
	toDebugLog("Got USR1");
	&init;
};
$SIG{USR2} = sub
{
	toDebugLog("Got USR2");
	&clear;
};
$SIG{PWR} = sub
{
	toDebugLog("Got SIGPWR ");
	&get_state;
};


&init;

my $pid = $cm{pid};
my $child_pid = fork();
die "Unable to fork : $!\n" if (!defined($child_pid)); 
if ($child_pid) 
{
	toDebugLog("Child pid: $child_pid , port $chld_id");
	open(PID, ">$pid") or die "Unable to create pid file $pid: $!";
	print PID "$child_pid\n";
	close PID;
}
else
{
	toDebugLog("[$$] Child started");
	toDebugLog("[$$] Closing child STDIN STDOUT STDERR");
	close STDIN;
	open(STDIN, '</dev/null');
	close STDOUT;
	open(STDOUT, '>/dev/null');
	close STDERR;
	open(STDERR, '>/dev/null');
	while (1)
	{ # endless loop back because sometimes conntrack can return EOF
		&processLogData;
	}
	toDebugLog("[$$] Child has finished.");
	exit(0);
}
toDebugLog("Close parent");
exit(0);






sub processLogData  
{
		toDebugLog("[$$] Open log file");
		open(LOG_FILE,"$cm{conntrack} -e DESTROY -E -o timestamp |") or die "Unable to open log $!\n";
		toDebugLog("[$$] Processing log data");
		while (<LOG_FILE>)  
		{
			if (/\[(.+?)\]\s+\[DESTROY\]\s+(.+?)\s+\d+\s+src=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+dst=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+sport=(\d+)\s+dport=(\d+)\s+packets=(\d+)\s+bytes=(\d+)\s+src=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+dst=(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+sport=(\d+)\s+dport=(\d+)\s+packets=(\d+)\s+bytes=(\d+)\s+\[ASSURED\]/) 
			{
				my ($dt, $type, $ip, $out_remote_ip, $out_local_port, $out_remote_port, $out_packets, $out_bytes, $in_src_ip, $in_remote_ip, $in_local_port, $in_remote_port, $in_packets, $in_bytes) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
				my $day = (localtime($dt))[3];
				if ($day != $current_day)
				{
					$current_day = $day;
					&clear;
				}
				$data_shared->{total}->{$ip}->{out} += $out_bytes;
				$data_shared->{total}->{$ip}->{in} += $in_bytes;
				$data_shared->{total}->{$ip}->{all} += $in_bytes + $out_bytes;
				if ($debug_level > 4)
				{
					toDebugLog("[$$] $ip, $out_remote_ip, total: $data_shared->{total}->{$ip}->{all}");
				}
				if (!exists($data_shared->{blocked_ip_list}->{$ip}) && exists($maxsize{$ip}) && $maxsize{$ip} < $data_shared->{total}->{$ip}->{all})
				{
					$data_shared->{blocked_ip_list}->{$ip} = $data_shared->{total}->{$ip}->{all};
					changeBlockedList();
					($debug_level > 2) && toDebugLog("[$$] $ip is blocked: max=$maxsize{$ip}  total=$data_shared->{blocked_ip_list}->{$ip}->{all}\n");
				}
				elsif (exists($data_shared->{blocked_ip_list}->{$ip}) && exists($maxsize{$ip}) && $maxsize{$ip} > $data_shared->{total}->{$ip}->{all})
				{
					($debug_level > 2) && toDebugLog("[$$] $ip removed from blocked list: max=$maxsize{$ip}  total=$data_shared->{total}->{$ip}->{all}\n");
					delete $data_shared->{blocked_ip_list}->{$ip};
					changeBlockedList();
				}
			}
		}
		close(LOG_FILE);
}


sub get_date
{
	my ($day, $month, $year) = (localtime)[3,4,5];
	return $day.$month.$year;
}



sub changeBlockedList
{
	my $blocked_ip_list = $data_shared->{blocked_ip_list};
	saveModuleConfigFile($cm{tcp_blocked}, $blocked_ip_list);
	my $xmlip = readConfigFile($ck{scfolder}.$ck{_ip_list});
	my $ip_items = $xmlip->{items}->{item};
	my @new_blocked;

	my $str = '';
	foreach (keys %$blocked_ip_list)
	{
		for (my $i=0; $i<=$#{$ip_items}; $i++)
		{
			if ($ip_items->[$i]->{ip} eq $_ && $ip_items->[$i]->{enabled})
			{
				push @new_blocked, $_;
				$str .= "Disabled $_ traffic.\nTotal: ".getHumanTrafficNumber($data_shared->{total}->{$_}->{all}, 'm')." MBytes.\nLimit: ".getHumanTrafficNumber($maxsize{$_}, 'm')." MBytes.\n";
			}
		}
	}
	toDebugLog($str);
	if ($str)
	{
		enableIPs(\@new_blocked,0);
	}
}


sub get_reset_ip
{
	if (-e "$ck{twmfolder}/modules/$mc/logs/$cm{reset_ip}")
	{
		open(FL, "$ck{twmfolder}/modules/$mc/logs/$cm{reset_ip}");
		my $ip = join("",<FL>);
		close(FL);
		unlink "$ck{twmfolder}/modules/$mc/logs/$cm{reset_ip}";
		$ip;
	}
}


sub clear
{
	toDebugLog("Clear log.");
	my $ip = &get_reset_ip;
	if ($ip)
	{
		delete $data_shared->{total}->{$ip} if (exists($data_shared->{total}->{$ip}));
		if (exists($data_shared->{blocked_ip_list}->{$ip}))
		{
			delete $data_shared->{blocked_ip_list}->{$ip};
			changeBlockedList();
		}
		
	}
	else
	{
		$data_shared->{total} = {};
		$data_shared->{blocked_ip_list} = {};
		changeBlockedList();
	}
	saveModuleConfigFile($nFConnLogging->get_log_filename(), $data_shared->{total});
	&init;
}


sub got_int 
{
	saveModuleConfigFile($nFConnLogging->get_log_filename(), $data_shared->{total});
	toDebugLog("Got SIGINT");
	unlink $pid;
	&closeLogData;
	$SIG{INT} = \&got_int;
	exit 0;
}


sub get_state
{
	saveModuleConfigFile($nFConnLogging->get_log_filename(), $data_shared->{total});
}


sub flush_conntrack
{
	run_script("$cm{conntrack} -F");
}



sub init
{
	toDebugLog("[$$] Initialization:");
	&flush_conntrack;
	&set_custom_restrictions;
	$data_shared->{blocked_ip_list} = {};
	$data_shared->{total} = readModuleConfigFile($nFConnLogging->get_log_filename());
	toDebugLog("[$$] Totals is:\n".Dumper($data_shared->{total}));
	toDebugLog("[$$] Read blocked ip");
	my $str = '';
	my $blocked = readModuleConfigFile($cm{tcp_blocked});
	$blocked->{pc} = [$blocked->{pc}] if ref $blocked->{pc} eq 'HASH';
	my $ip_blocked = $nFConnLogging->get_blocked();
	for (keys %$blocked)
	{
		next if !$_;
		if (exists($ip_blocked->{$_}) && $ip_blocked->{$_})
		{
			$data_shared->{blocked_ip_list}->{$_} = 1;
			$str .= "	$_ is blocked\n";
		}
		else
		{
			$str .= "	$_ is NOT blocked\n";
		}
	}
	toDebugLog($str||"[$$] There is not any new blocked ip");
	$str = '';
	toDebugLog("[$$] Read log file to add new blocked ip.");
	my $is_new_blocked = 0;
	for my $ip (keys %maxsize)  
	{
		if (!exists($data_shared->{blocked_ip_list}->{$ip}) && exists($maxsize{$ip}) && exists($data_shared->{total}->{$ip}) && $maxsize{$ip} <= $data_shared->{total}->{$ip}->{all})
		{
			$is_new_blocked = 1;
			$data_shared->{blocked_ip_list}->{$ip} = $data_shared->{total}->{$ip};
			$str .= "	$ip is blocked: max=$maxsize{$ip}  total=$data_shared->{total}->{$ip}\n";
		}
	}
	toDebugLog($str||"[$$] There is not any new ip to block");
	$is_new_blocked && changeBlockedList();
	toDebugLog(Dumper $data_shared);
}


###########################################################
# Set default restriction for all ip
###########################################################
sub set_default_restrictions  
{
	%maxsize = ();
	my $ip_list = &getIPs;
	my $default_size = $cm{default_size}*1024*1024;
	$maxsize{$_->{ip}} = $default_size for (@$ip_list);
	toDebugLog("[$$] Set default size $default_size for all ip");
}


###########################################################
# Set custom restriction for all ip
###########################################################
sub set_custom_restrictions
{
	&set_default_restrictions;
	my $str = "[$$] Set custom restrictions:\n";
	my $xml = &readModuleConfigFile($cm{tcp_limit});
	$xml->{'pc'} = [$xml->{'pc'}] if ref $xml->{'pc'} eq 'HASH';
	$maxsize{$_->{ip}} = $_->{size}*1024*1024 for (@{$xml->{'pc'}});
	toDebugLog($str.Dumper(%maxsize));
}




sub closeLogData  
{
	toDebugLog("[$$] Close log");
	run_script("kill -9 `ps ax | grep 'conntrack -e DESTROY -E -o timestamp' | gawk '{print $1}'`");
#	close(LOG_FILE) or toErrorLog("[$$] Can not close log file $!");
}


#############################################################
# check died children
#############################################################
sub reaper
{
	toDebugLog("[$$] Reaper @_");
	if ($HAS_POSIX)
	{
		foreach my $pid (keys %child_process) 
		{
			my $res = $HAS_POSIX ? waitpid($pid, WNOHANG) : waitpid($pid,0);
			if ($res > 0) 
			{
				delete $child_process{$pid};
				toDebugLog("[$$] Removed \$pid=$pid");
			}
		}
	} 
	else
	{
		my $waitedpid = 0;
		while ($waitedpid != -1) 
		{
			$waitedpid = wait;
		}
	}

        if ($old_child_handler && ref $old_child_handler eq 'CODE')
        {
            &$old_child_handler;
        }
}

sub got_SIG
{
  my $signal = shift;
  toDebugLog("[$$] Got [$signal] signal. Ignore!");
}

